package com.ficus.road.maintain.service.carmanager;

import com.ficus.road.maintain.core.model.carmanager.*;
import com.ficus.road.maintain.core.model.dingtalk.DingTalkUserInfo;
import com.ficus.road.maintain.core.model.dingtalk.message.DingTalkOpenMessage;
import com.ficus.road.maintain.core.model.dingtalk.message.MarDownMessage;
import com.ficus.road.maintain.util.MyDateUtil;
import com.ficus.road.maintain.util.dingtalk.DingTalkUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.stream.Collectors;

/**
 * @author xiawen yang
 * @date 2022/2/17 下午3:48
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class CarManagerService {

    public static final Map<String, Long> CAR_DOWN_MAP = new ConcurrentHashMap<>();
    public static final Map<String, Long> CAR_UP_MAP = new ConcurrentHashMap<>();
    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    private final CarInfoService carInfoService;
    private final CarStatusService carStatusService;
    private final DingTalkUtil dingTalkUtil;
    private final ThreadPoolExecutor carBindExecutorPool = new ThreadPoolExecutor(5, 10,
            0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(20), new CustomizableThreadFactory("carBindExecutor-pool-"));
    @Value("${car_manager.car_down_gap}")
    private long carDownGap;
    @Value("${car_manager.car_up_gap}")
    private long carUpGap;

    public BindCarInput convert2BindCarInput(CarBindRequest carBindRequest, DingTalkUserInfo dingTalkUserInfo) {
        BindCarInput bindCarInput = BindCarInput.builder()
                .carId(carBindRequest.getCarId())
                .userInfo(dingTalkUserInfo)
                .bindCheckCar(carBindRequest.getBindCheckCar())
                .bindCarImage(carBindRequest.getBindCarImage())
                .bindCheckCamera(carBindRequest.getBindCheckCamera())
                .bindCheckCeiling(carBindRequest.getBindCheckCeiling())
                .bindBuckleImages(carBindRequest.getBindBuckleImages())
                .bindCheckBoot(carBindRequest.getBindCheckBoot())
                .bindBattery(carBindRequest.getBindBattery())
                .bindCheckStatus(carBindRequest.getBindCheckStatus())
                .bindCheckAirConditioning(carBindRequest.getBindCheckAirConditioning())
                .bindInfo(carBindRequest.getBindInfo())
                .build();
        return bindCarInput;
    }

    public UnbindCarInput convert2UnbindCarInput(CarUnbindRequest carUnbindRequest, DingTalkUserInfo dingTalkUserInfo) {
        UnbindCarInput unbindCarInput = UnbindCarInput.builder()
                .userInfo(dingTalkUserInfo)
                .unbindCheckBatteryOff(carUnbindRequest.getUnbindCheckBatteryOff())
                .unbindCheckBattery(carUnbindRequest.getUnbindCheckBattery())
                .unbindCheckCeiling(carUnbindRequest.getUnbindCheckCeiling())
                .unbindBuckleImages(carUnbindRequest.getUnbindBuckleImages())
                .unbindCheckCamera(carUnbindRequest.getUnbindCheckCamera())
                .unbindCheckCar(carUnbindRequest.getUnbindCheckCar())
                .unbindCarImage(carUnbindRequest.getUnbindCarImage())
                .unbindInfo(carUnbindRequest.getUnbindInfo())
                .build();
        return unbindCarInput;
    }

    public void processPrometheusAlerts(WebhookMessage webhookMessage) {
//        log.info("当前线程是否是守护线程: {}", Thread.currentThread().isDaemon());
        List<Alert> firingAlerts = webhookMessage.getAlerts().stream().filter(alert -> "firing".equals(alert.getStatus())).collect(Collectors.toList());
        log.info("处理prometheus告警, 告警信息: {}", firingAlerts);
        log.info("WebhookMessage: {}", webhookMessage);
        for (Alert alert : firingAlerts) {
            Map<String, String> labels = alert.getLabels();
            if (ObjectUtils.isNotEmpty(labels)) {
                if ("CarUp".equals(labels.get("alertname"))) {
//                    processCarUp(labels);
                    processCarUpAsync(labels);
                } else if ("CarDown".equals(labels.get("alertname"))) {
                    processCarDown(labels);
                }
            }
        }
    }

    public void processCarUp(Map<String, String> labels) {
        String carId = labels.get("instance");
        if (ObjectUtils.isNotEmpty(carId)) {
            CarInfo carInfo = carInfoService.findByCarId(carId);
            if (ObjectUtils.isEmpty(carInfo)) {
                log.error("processCarUp Alerts error, can not find car by carId!");
            }
            CarStatus bindStatus = carStatusService.queryBindUserByCarId(carId);
            // 启用default_user
            String userId = carInfo.getDefaultUserId();
            if (ObjectUtils.isNotEmpty(bindStatus)) {
                // 如果没有车辆绑定信息, 就把上线通知发给default_user_id, 如果有, 就发给绑定的司机
                userId = bindStatus.getUserid();
            }
            // 发送车辆上线通知: 距离上次下线时间不超过十分钟, 就不发送上线通知
            Long lastDownTime = CAR_DOWN_MAP.get(carId);
            if (CAR_DOWN_MAP.containsKey(carId) && System.currentTimeMillis() - lastDownTime < carDownGap) {
                log.info("车辆: {}, 上次下线时间为{}, 不发送上线通知", carId, sdf.format(lastDownTime));
                return;
            }
            // 距离上次上线时间不超过40分钟, 就不发送上线通知
            Long lastUpTime = CAR_UP_MAP.get(carId);
            if (CAR_UP_MAP.containsKey(carId) && System.currentTimeMillis() - lastUpTime < carUpGap) {
                log.info("车辆: {}, 上次上线时间为{}, 不发送上线通知", carId, sdf.format(lastUpTime));
                return;
            }
            String text = String.format("# 车辆上线通知\n- 车牌：%s\n- 时间：%s\n",
                    carInfo.getLicensePlateNumber(),
                    MyDateUtil.dateFormatStr(new Date(), "yyyy-MM-dd HH:mm:ss"));
            DingTalkOpenMessage message = DingTalkOpenMessage
                    .builder()
                    .msgType("markdown")
                    .markDown(MarDownMessage.builder().title("车辆上线通知").text(text).build()).build();
            try {
                dingTalkUtil.corpMessageAsyncSend(Collections.singletonList(userId), null, Boolean.FALSE, message);
            } catch (Exception e) {
                e.printStackTrace();
            }
            CAR_UP_MAP.put(carId, System.currentTimeMillis());
        }
    }

    /**
     * 最长等待时间：30m
     *
     * @param labels
     */
    public void processCarUpAsync(Map<String, String> labels) {
        String carId = labels.get("instance");
        if (ObjectUtils.isNotEmpty(carId)) {
            // 发送车辆上线通知: 距离上次下线时间不超过十分钟, 就不发送上线通知
            CarInfo carInfo = carInfoService.findByCarId(carId);
            if (ObjectUtils.isEmpty(carInfo)) {
                // 没找到车辆id, 直接返回了, 不会中断queryBindUserAsync中的线程
                log.error("processCarUp Alerts error, can not find car by carId!");
                return;
            }

            //车辆在线, 直接返回
            if (isCarOnline(carId)) {
                return;
            }

            // 提交异步任务
            Future<CarStatus> carStatusFuture = carBindExecutorPool.submit(() -> queryBindUserAsync(carId));
            try {
                // 阻塞调用
                CarStatus carStatus = carStatusFuture.get(30, TimeUnit.MINUTES);
                if (ObjectUtils.isNotEmpty(carStatus)) {
                    String text = String.format("# 车辆上线通知\n- 车牌：%s\n- 时间：%s\n",
                            carInfo.getLicensePlateNumber(),
                            MyDateUtil.dateFormatStr(new Date(), "yyyy-MM-dd HH:mm:ss"));
                    DingTalkOpenMessage message = DingTalkOpenMessage
                            .builder()
                            .msgType("markdown")
                            .markDown(MarDownMessage.builder().title("车辆上线通知").text(text).build()).build();
                    dingTalkUtil.corpMessageAsyncSend(Collections.singletonList(carStatus.getUserid()), null, Boolean.FALSE, message);
                }
            } catch (TimeoutException e) {
                carStatusFuture.cancel(true);
                log.error("获取车辆{}绑定信息超时: {}", carId, e.getLocalizedMessage());
                return;
            } catch (InterruptedException e) {
                carStatusFuture.cancel(true);
                log.error("获取车辆{}绑定信息中断: {}", carId, e.getLocalizedMessage());
                return;
            } catch (ExecutionException e) {
                carStatusFuture.cancel(true);
                log.error("获取车辆{}绑定信息执行错误: {}", carId, e.getMessage());
                return;
            } catch (Exception e) {
                e.printStackTrace();
                return;
            }
            CAR_UP_MAP.put(carId, System.currentTimeMillis());
            log.info("当前上线车辆信息: {}", CAR_UP_MAP);
        }
    }

    public CarStatus queryBindUserAsync(String carId) {
        CarStatus bindStatus = carStatusService.queryBindUserByCarId(carId);
        try {
            while (!Thread.currentThread().isInterrupted() && ObjectUtils.isEmpty(bindStatus)) {
                log.info("车辆id: {}未找到绑定记录, 等待司机绑定后再发送上线通知, 等待时间： 3分钟", carId);
                TimeUnit.MINUTES.sleep(3);
                bindStatus = carStatusService.queryBindUserByCarId(carId);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return bindStatus;
    }

    /**
     * 判断车辆是否在线
     *
     * @param carId
     * @return
     */
    public boolean isCarOnline(String carId) {
        Long lastDownTime = CAR_DOWN_MAP.get(carId);
        if (CAR_DOWN_MAP.containsKey(carId) && System.currentTimeMillis() - lastDownTime < carDownGap) {
            log.info("车辆: {}, 上次下线时间为{}, 不发送上线通知", carId, sdf.format(lastDownTime));
            return true;
        }
        // 距离上次上线时间不超过40分钟, 就不发送上线通知
        Long lastUpTime = CAR_UP_MAP.get(carId);
        if (CAR_UP_MAP.containsKey(carId) && System.currentTimeMillis() - lastUpTime < carUpGap) {
            log.info("车辆: {}, 上次上线时间为{}, 不发送上线通知", carId, sdf.format(lastUpTime));
            return true;
        }
        return false;
    }

    /**
     * 更新车辆最近一次的下线时间
     *
     * @param labels
     */
    public void processCarDown(Map<String, String> labels) {
        String carId = labels.get("instance");
        CAR_DOWN_MAP.put(carId, System.currentTimeMillis());
    }

    public void clearCarRecord(String carId) {
        CAR_UP_MAP.remove(carId);
        CAR_DOWN_MAP.remove(carId);
    }
}
